﻿using FlowSequencer.Core.FlowElements;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace FlowSequencer.Core.Sequencer
{
    public class EarliestDueDate : Sequencer
    {
        Flow optimalFlow;
        public EarliestDueDate() 
        {
            optimalFlow = new Flow();
            optimalFlow.Lines.Add(new Line());
            optimalFlow.Lines[0].Name = "OptimalSequence";
        }

        
        public override void ComputeFlow()
        {
            optimalFlow.DownTime = 0;
            var overallProcessTime = inputFlow.Lines[0].Tasks.Sum(x => x.Cycles);
            int previousId = -1;
            for (int time = 0; time < overallProcessTime; time++)
            {

                var readyTaks = GetReadyTasks(time);
                var taskWithTheEarliestFinishTime = GetTaskWithEarliestDueDate(readyTaks);
                
                if (taskWithTheEarliestFinishTime == null)
                {
                    overallProcessTime = overallProcessTime + 1;
                    optimalFlow.Lines[0].Tasks.Add(new Task() { Index = -1, Cycles = 1, Name = "" });
                    continue;
                }

                optimalFlow.OptimalSequence.Add(taskWithTheEarliestFinishTime.Index);
                inputFlow.Lines[0].Tasks[taskWithTheEarliestFinishTime.Index].Cycles--;
                if(previousId == taskWithTheEarliestFinishTime.Index)
                {
                    var prevItem = optimalFlow.Lines[0].Tasks.Where(x=>x.Index == previousId).First();
                    var idnex = optimalFlow.Lines[0].Tasks.IndexOf(prevItem);
                    optimalFlow.Lines[0].Tasks[idnex].Cycles++;
                }
                else
                {
                    optimalFlow.Lines[0].Tasks.Add(new Task() { Index = taskWithTheEarliestFinishTime.Index, Cycles = 1, Name = taskWithTheEarliestFinishTime.Name });
                }

                CheckMaxDownTime(taskWithTheEarliestFinishTime.Index,time);       
                previousId = taskWithTheEarliestFinishTime.Index;
            }
         
        }

        private void CheckMaxDownTime(int cIndex, int ctime)
        {
            var expectedEndTime = inputFlow.Lines[2].Tasks.FirstOrDefault(x => x.Index == cIndex);
            int cDownTime = ctime - expectedEndTime.Cycles + 1;
        
             if (optimalFlow.DownTime < cDownTime)
             {
                optimalFlow.DownTimeTaskName = expectedEndTime.Name;
                optimalFlow.DownTime = cDownTime;
            }
             else if (optimalFlow.DownTime == cDownTime)
            {
                optimalFlow.DownTimeTaskName += "," + expectedEndTime.Name;
            }
             
        }

        private static Task GetTaskWithEarliestDueDate(IEnumerable<Task> readyTaks)
        {
            var taskWithTheEarliestFinishTime = readyTaks.Where(x => x.Cycles == readyTaks.Min(y => y.Cycles)).FirstOrDefault();
            return taskWithTheEarliestFinishTime;
        }

        private IEnumerable<Task> GetReadyTasks(int time)
        {
            var readyTaks =
                from processTime in inputFlow.Lines[0].Tasks
                join readyTime in inputFlow.Lines[1].Tasks on processTime.Index equals readyTime.Index
                join expectedFinishTime in inputFlow.Lines[2].Tasks on readyTime.Index equals expectedFinishTime.Index
                where readyTime.Cycles <= time
                where processTime.Cycles > 0
                select expectedFinishTime;
            return readyTaks;
        }

        public override Flow GetOptimizedFlow()
        {
            return optimalFlow;
        }
    }
}
